use firework::Drawable;
use std::{
  io::{Error, Stdout, Write},
  sync::mpsc::Receiver,
  thread,
  time::Duration,
};
use termion::{color::Rgb, cursor::Goto};
mod colors;
mod firework;
mod state_machine;

pub fn start(stdout: &mut Stdout, receiver: Receiver<bool>, seed: u64) {
  let mut state_machine = state_machine::StateMachine::new(seed, 24);
  render_loop(&mut state_machine, stdout, receiver);
}

fn render_loop(
  state_machine: &mut state_machine::StateMachine,
  stdout: &mut Stdout,
  receiver: Receiver<bool>,
) {
  write!(stdout, "{}{}", termion::clear::All, termion::cursor::Hide)
    .expect("Unable to clear terminal for writing");

  let (mut prev_length, mut prev_width) = terminal_length_width();

  loop {
    output_grid(
      &state_machine.tick(prev_width),
      stdout,
      prev_length,
      prev_width,
    )
    .expect("Unable to write to terminal. Exiting.");

    let (length, width) = terminal_length_width();
    prev_length = length;
    prev_width = width;

    if let Ok(v) = receiver.try_recv() {
      if v {
        write!(stdout, "{}{}", termion::clear::All, termion::cursor::Show)
          .expect("Unable to restore terminal");
        return;
      }
    }

    thread::sleep(Duration::from_millis(75));
  }
}

fn output_grid(
  drawables: &[&dyn Drawable],
  stdout: &mut Stdout,
  prev_length: u16,
  prev_width: u16,
) -> Result<(), Error> {
  let (length, width) = terminal_length_width();

  if length != prev_length || width != prev_width {
    // Even though it's slower, clear the whole screen.
    // We won't be able to manually clear everything since the coords moved.
    write!(stdout, "{}", termion::clear::All)?;
  } else {
    // otherwise clear every point manually (faster)
    for drawable in drawables.iter() {
      let points = drawable.clear();
      for point in points.iter() {
        if point.y < width {
          write!(
            stdout,
            "{}{} ",
            Goto(point.x % length, width - point.y),
            termion::cursor::Hide,
          )?;
        }
      }
    }
  }

  for drawable in drawables.iter() {
    let points = drawable.draw();
    for point in points.iter() {
      if point.y < width {
        write!(
          stdout,
          "{}{}{}*",
          Goto(point.x % length, width - point.y),
          termion::cursor::Hide,
          Rgb(
            point.pixel_color.r,
            point.pixel_color.g,
            point.pixel_color.b
          )
          .fg_string()
        )?;
      }
    }
  }

  stdout.flush()?;

  Ok(())
}

fn terminal_length_width() -> (u16, u16) {
  termion::terminal_size().expect("Unable to get terminal size")
}
